// AnalyzeDlg.cpp : implementation file
//

#include "stdafx.h"
#include "ActionTrace.h"
#include "AnalyzeDlg.h"


// CAnalyzeDlg dialog

IMPLEMENT_DYNAMIC(CAnalyzeDlg, CDialog)

CAnalyzeDlg::CAnalyzeDlg(CWnd* pParent /*=NULL*/)
: CDialog(CAnalyzeDlg::IDD, pParent), m_pRecords(NULL), m_lRecCount(0), m_strLogPath(NULL)
, m_bWrongFile(false)
, m_lFrom(0)
, m_lTo(0)
{
	ZeroMemory(m_strApps, APP_COUNT*MAX_PATH*sizeof(TCHAR));
	ZeroMemory(&m_logHeader, sizeof(LogHeader));
}

CAnalyzeDlg::~CAnalyzeDlg()
{
	if (m_pRecords)
	{
		free(m_pRecords);
		m_pRecords = 0;
	}
}

void CAnalyzeDlg::DoDataExchange(CDataExchange* pDX)
{
	CDialog::DoDataExchange(pDX);
	DDX_Control(pDX, IDC_EDIT_PATH, m_ePath);
	DDX_Control(pDX, IDC_TREE_APPS, m_treeApps);
	DDX_Control(pDX, IDC_EDIT_DISPLAY, m_editDisplay);
	DDX_Control(pDX, IDC_STATIC_INFO, m_staticInfo);
	DDX_Control(pDX, IDC_DATETIMEPICKER_FROM, m_dateFrom);
	DDX_Control(pDX, IDC_DATETIMEPICKER_TO, m_dateTo);
}


BEGIN_MESSAGE_MAP(CAnalyzeDlg, CDialog)
	ON_BN_CLICKED(IDC_BTN_BROWSE, &CAnalyzeDlg::OnBnClickedBtnBrowse)
	ON_NOTIFY(TVN_SELCHANGED, IDC_TREE_APPS, &CAnalyzeDlg::OnTvnSelchangedTreeApps)
	ON_EN_CHANGE(IDC_EDIT_PATH, &CAnalyzeDlg::OnEnChangeEditPath)
	ON_WM_CTLCOLOR()
	ON_BN_CLICKED(IDB_ALL, &CAnalyzeDlg::OnBnClickedAll)
	ON_BN_CLICKED(IDB_TODAY, &CAnalyzeDlg::OnBnClickedToday)
	ON_NOTIFY(DTN_DATETIMECHANGE, IDC_DATETIMEPICKER_FROM, &CAnalyzeDlg::OnDtnDatetimechangeDatetimepickerFrom)
	ON_NOTIFY(DTN_DATETIMECHANGE, IDC_DATETIMEPICKER_TO, &CAnalyzeDlg::OnDtnDatetimechangeDatetimepickerTo)
END_MESSAGE_MAP()

BOOL CAnalyzeDlg::OnInitDialog()
{
	CDialog::OnInitDialog();
	EnableTimeRange(false);

	m_ePath.SetWindowText(m_strLogPath);
	m_ePath.SetSel(-1, 0);

	LOGFONT lf;
	CFont *f = m_staticInfo.GetFont();
	f->GetLogFont(&lf);
	lf.lfHeight *= 0.92;
	m_fontInfo.CreateFontIndirect(&lf);
	m_staticInfo.SetFont(&m_fontInfo);

	m_dateFrom.SetFormat(_T("yyyy-MM-dd HH:mm:ss"));
	m_dateTo.SetFormat(_T("yyyy-MM-dd HH:mm:ss"));
	return TRUE;
}


// CAnalyzeDlg message handlers

void CAnalyzeDlg::OnBnClickedBtnBrowse()
{
	CFileDialog dlg(TRUE, _T(".rec"), _T("track.rec"), OFN_ENABLEHOOK, _T("Record binary log (*.rec)|*.rec|All files|*.*||"));
	if (dlg.DoModal() == IDOK)
	{
		CString fileName = dlg.GetPathName();
		m_ePath.SetWindowText(fileName.GetString());
	}
}

void CAnalyzeDlg::OnTvnSelchangedTreeApps(NMHDR *pNMHDR, LRESULT *pResult)
{
	LPNMTREEVIEW pNMTreeView = reinterpret_cast<LPNMTREEVIEW>(pNMHDR);
	if (pNMTreeView->itemNew.hItem == NULL)
		return;
	CAppNode *node = reinterpret_cast<CAppNode *>(pNMTreeView->itemNew.lParam);
	if (!node)
	{
		CAppNode sum = SumSubNodes(pNMTreeView->itemNew.hItem);
		Display(&sum);
		HTREEITEM hChild = m_treeApps.GetChildItem(pNMTreeView->itemNew.hItem);
		return;
	}
	Display(node);
	*pResult = 0;
}

CAppNode CAnalyzeDlg::SumSubNodes(HTREEITEM hItem)
{
	TVITEM item;
	item.hItem = hItem;
	item.mask = TVIF_HANDLE | TVIF_PARAM;
	m_treeApps.GetItem(&item);
	CAppNode *tmp = reinterpret_cast<CAppNode *>(item.lParam);
	if (tmp)
		return *tmp;

	CAppNode sum;
	HTREEITEM hCurr = m_treeApps.GetChildItem(hItem);
	if (hCurr == NULL)
	{
		return sum;
	}

	while (hCurr)
	{
		sum += SumSubNodes(hCurr);
		hCurr = m_treeApps.GetNextSiblingItem(hCurr);
	}
	return sum;
}

void CAnalyzeDlg::Display(CAppNode *pNode)
{
	CString output;
	output.Append(_T("Full Path:\r\n"));
	output.Append(_T("-----------------------------------------\r\n"));
	output.AppendFormat(_T("  - %s\r\n"), pNode->GetFullPath());
	output.Append(_T("\r\n"));

	output.AppendFormat(_T("Mouse Button Total Click:\t%d\r\n"), pNode->GetMouseTotal());
	output.Append(_T("-----------------------------------------\r\n"));
	output.AppendFormat(_T("  - Left Button Click:\t\t%d\r\n"), pNode->GetMouseLeftClick());
	output.AppendFormat(_T("  - Right Button Click:\t%d\r\n"), pNode->GetMouseRightClick());
	output.AppendFormat(_T("  - Other Button Click:\t%d\r\n"), pNode->GetMouseTotal() - pNode->GetMouseLeftClick() - pNode->GetMouseRightClick());
	output.Append(_T("\r\n"));

	output.AppendFormat(_T("Keyboard Keys Total Press:\t%d\r\n"), pNode->GetKeyTotal());
	output.Append(_T("-----------------------------------------\r\n"));
	output.AppendFormat(_T("  - Char Key Press:\t\t%d\r\n"), pNode->GetKeyAlphabet());
	output.AppendFormat(_T("  - Digit Key Press:\t\t%d\r\n"), pNode->GetKeyDigit());
	output.AppendFormat(_T("  - Extension Key Press:\t%d\r\n"), pNode->GetKeyExt());
	output.AppendFormat(_T("  - Other Key Press:\t\t%d\r\n"), pNode->GetKeyOther());
	m_editDisplay.SetWindowText(output.GetString());
}

void CAnalyzeDlg::OnEnChangeEditPath()
{
	CString fileName;
	m_ePath.GetWindowText(fileName);
	if (fileName.GetLength() == 0)
		return;

	//clear exsiting data
	Clear();

	FILE *fp = _tfopen(fileName.GetString(), _T("rb"));
	if (!fp)
	{
		MessageBox(_T("File can not be readed!"), _T("Read error"));
		m_bWrongFile = true;
		m_staticInfo.SetWindowText(_T("File can not be readed!"));
		EnableTimeRange(false);
		return;
	}
	int i = 0;

	//1. read header and apps
	if (fread(&m_logHeader, sizeof(LogHeader), 1, fp) != 1 || m_logHeader.signature != 0xAAAAAAAA)
	{
		MessageBox(_T("Selected file is not log of ActionTrace!"), _T("File error"));
		m_bWrongFile = true;
		m_staticInfo.SetWindowText(_T("Selected file is not log of ActionTrace!"));
		EnableTimeRange(false);
		return;
	}
	CString info;
	info.AppendFormat(_T("File Version: %d.%d;    Encoding: %s;    Info: %s"), HIWORD(m_logHeader.version), LOWORD(m_logHeader.version), (m_logHeader.encoding == 1)? _T("ANSI"): _T("UNICODE"), m_logHeader.info);
	m_staticInfo.SetWindowText(info.GetString());
	fread(m_strApps, MAX_PATH * sizeof(TCHAR), APP_COUNT, fp);

	//2. read data
	long pos = ftell(fp);
	fseek(fp, 0, SEEK_END);
	long size = ftell(fp) - pos;
	if (size == 0)
	{
		MessageBox(_T("There's not any record! Stop the tracing and try again, or select another rec log file manually."), _T("No record"));
		EnableTimeRange(false);
		return;
	}
	fseek(fp, pos, SEEK_SET);
	m_pRecords = (Record *)malloc(size);
	if (fread(m_pRecords, size, 1, fp) != 1)
	{
		MessageBox(_T("File read error!"), _T("Read error"));
		free(m_pRecords);
		m_pRecords = NULL;
		EnableTimeRange(false);
		return;
	}
	fclose(fp);

	//3. inital data
	CAppNode node;
	while (lstrcmp(m_strApps[i], _T("")))
	{
		node.m_iAppId = i;
		node.m_strFullPath = m_strApps[i];
		int j = lstrlen(m_strApps[i]) - 1;
		while (m_strApps[i][j--] != _T('\\'));
		node.m_strFileName = &m_strApps[i++][j+2];
		m_vAppNodes.push_back(node);
	}

	m_lRecCount = size/sizeof(Record);
	m_lFrom = 0;
	m_lTo = m_lRecCount - 1;

	ProcessData();

	//4. update time range
	COleDateTime lower(m_pRecords[0].time);
	COleDateTime upper(m_pRecords[m_lTo].time);
	m_dateFrom.SetRange(&lower, &upper);
	m_dateTo.SetRange(&lower, &upper);
	m_dateFrom.SetTime(lower);
	m_dateTo.SetTime(upper);
	EnableTimeRange();

	//5. update tree
	HTREEITEM hRoot = m_treeApps.InsertItem(_T("Total"));
	i = 0;
	while (lstrcmp(m_strApps[i], _T("")))
	{
		TVINSERTSTRUCT tvInsert;
		ZeroMemory(&tvInsert, sizeof(TVINSERTSTRUCT));
		tvInsert.hParent = hRoot;
		tvInsert.hInsertAfter = TVI_LAST;
		tvInsert.itemex.mask = TVIF_TEXT | TVIF_PARAM;
		tvInsert.itemex.pszText = m_vAppNodes[i].m_strFileName;
		tvInsert.itemex.lParam = reinterpret_cast<LPARAM>(&m_vAppNodes[i++]);
		m_treeApps.InsertItem(&tvInsert);
	}
	m_treeApps.Expand(hRoot, TVE_EXPAND);

	//6. select root node
	m_treeApps.Select(hRoot, TVGN_CARET);
}

HBRUSH CAnalyzeDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
{
	HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor);

	// TODO:  Change any attributes of the DC here
	if (pWnd->GetDlgCtrlID() == IDC_STATIC_INFO)
	{
		if (m_bWrongFile)
			pDC->SetTextColor(RGB(204, 0, 51));
		else
			pDC->SetTextColor(RGB(36, 94, 220));
	}

	// TODO:  Return a different brush if the default is not desired
	return hbr;
}

void CAnalyzeDlg::OnBnClickedAll()
{
	COleDateTime from(m_pRecords[0].time);
	COleDateTime to(m_pRecords[m_lRecCount-1].time);
	COleDateTime time;
	m_dateFrom.GetTime(time);
	if (time != from)
	{
		m_dateFrom.SetTime(from);
		OnDtnDatetimechangeDatetimepickerFrom(NULL, NULL);
	}
	m_dateTo.GetTime(time);
	if (time != to)
	{
		m_dateTo.SetTime(to);
		OnDtnDatetimechangeDatetimepickerTo(NULL, NULL);
	}
}

void CAnalyzeDlg::OnBnClickedToday()
{
	time_t t = time(NULL);
	struct tm *timeinfo = localtime(&t);
	timeinfo->tm_hour = 0;
	timeinfo->tm_min = 0;
	timeinfo->tm_sec = 0;
	time_t newt = mktime(timeinfo);
	m_dateFrom.SetTime(max(m_pRecords[0].time, newt));

	COleDateTime to(m_pRecords[m_lRecCount-1].time);
	m_dateTo.SetTime(to);

	OnDtnDatetimechangeDatetimepickerFrom(NULL, NULL);
	OnDtnDatetimechangeDatetimepickerTo(NULL, NULL);
}
void CAnalyzeDlg::EnableTimeRange(bool enable)
{
	m_dateFrom.EnableWindow(enable);
	m_dateTo.EnableWindow(enable);
	GetDlgItem(IDB_TODAY)->EnableWindow(enable);
	GetDlgItem(IDB_ALL)->EnableWindow(enable);
}

void CAnalyzeDlg::Clear()
{
	if (m_pRecords)
	{
		free(m_pRecords);
		m_pRecords = NULL;
	}
	m_treeApps.DeleteAllItems();
	m_vAppNodes.clear();
	m_bWrongFile = false;
	m_lFrom = 0;
	m_lTo = 0;
}

void CAnalyzeDlg::ProcessData()
{
	for (size_t i=0; i<m_vAppNodes.size(); i++)
	{
		m_vAppNodes[i].Clear();
	}
	for (long i=m_lFrom; i<=m_lTo; i++)
	{
		CAppNode &node = m_vAppNodes[m_pRecords[i].app];
		if (m_pRecords[i].type == 0)	//mouse
		{
			node.m_uMouse[m_pRecords[i].value]++;
		}
		else							//keyboard
		{
			int index;
			short val = m_pRecords[i].value;
			if (val >= 0x41 && val <= 0x5A)
				index = 0;
			else if (val >=0x30 && val <= 0x39)
				index = 1;
			else if (m_pRecords[i].param&1)
				index = 2;
			else
				index = 3;
			if (!((m_pRecords[i].param>>7)&1))
				index += 4;
			node.m_uKeyboard[index]++;
		}
	}
}
void CAnalyzeDlg::OnDtnDatetimechangeDatetimepickerFrom(NMHDR *pNMHDR, LRESULT *pResult)
{
	//LPNMDATETIMECHANGE pDTChange = reinterpret_cast<LPNMDATETIMECHANGE>(pNMHDR);
	SYSTEMTIME st;
	m_dateFrom.GetTime(&st);
	struct tm timeinfo = {0};
	timeinfo.tm_year = st.wYear - 1900;
	timeinfo.tm_mon = st.wMonth - 1;
	timeinfo.tm_mday = st.wDay;
	timeinfo.tm_hour = st.wHour;
	timeinfo.tm_min = st.wMinute;
	timeinfo.tm_sec = st.wSecond;
	time_t t = mktime(&timeinfo);
	int i = 0;
	while (m_pRecords[i++].time < t);
	m_lFrom = i - 1;

	ProcessData();
	HTREEITEM hSel = m_treeApps.GetSelectedItem();
	m_treeApps.SelectItem(NULL);
	m_treeApps.SelectItem(hSel);
	//*pResult = 0;
}

void CAnalyzeDlg::OnDtnDatetimechangeDatetimepickerTo(NMHDR *pNMHDR, LRESULT *pResult)
{
	//LPNMDATETIMECHANGE pDTChange = reinterpret_cast<LPNMDATETIMECHANGE>(pNMHDR);
	SYSTEMTIME st;
	m_dateTo.GetTime(&st);
	struct tm timeinfo = {0};
	timeinfo.tm_year = st.wYear - 1900;
	timeinfo.tm_mon = st.wMonth - 1;
	timeinfo.tm_mday = st.wDay;
	timeinfo.tm_hour = st.wHour;
	timeinfo.tm_min = st.wMinute;
	timeinfo.tm_sec = st.wSecond;
	time_t t = mktime(&timeinfo);
	int i = m_lRecCount - 1;
	while (m_pRecords[i--].time > t);
	m_lTo = i + 1;

	ProcessData();
	HTREEITEM hSel = m_treeApps.GetSelectedItem();
	m_treeApps.SelectItem(NULL);
	m_treeApps.SelectItem(hSel);
	//*pResult = 0;
}
